'use strict';

const FS = require('fs');
const PATH = require('path');
const { optimize } = require('svgo');
const regSVGFile = /\.svg$/i;

/**
 * Synchronously check if path is a directory. Tolerant to errors like ENOENT.
 * @param {string} path
 */
function checkIsDir(path) {
  try {
    return FS.lstatSync(path).isDirectory();
  } catch (e) {
    return false;
  }
}

async function svgoAction(input, output, config) {
  config.exclude = []
  if (input && output && checkIsDir(input)) {
    await optimizeFolder(config, input, output);
  } else{
    console.log('no input directory');
  }
}

exports.svgoAction = svgoAction;

/**
 * Optimize SVG files in a directory.
 * @param {Object} config options
 * @param {string} dir input directory
 * @param {string} output output directory
 * @return {Promise}
 */
function optimizeFolder(config, dir, output) {
  if (!config.quiet) {
    console.log(`Processing directory '${dir}':\n`);
  }
  return FS.promises
    .readdir(dir)
    .then((files) => processDirectory(config, dir, files, output));
}

/**
 * Process given files, take only SVG.
 * @param {Object} config options
 * @param {string} dir input directory
 * @param {Array} files list of file names in the directory
 * @param {string} output output directory
 * @return {Promise}
 */
function processDirectory(config, dir, files, output) {
  // take only *.svg files, recursively if necessary
  var svgFilesDescriptions = getFilesDescriptions(config, dir, files, output);

  return svgFilesDescriptions.length
    ? Promise.all(
        svgFilesDescriptions.map((fileDescription) =>
          optimizeFile(
            config,
            fileDescription.inputPath,
            fileDescription.outputPath
          )
        )
      )
    : Promise.reject(
        new Error(`No SVG files have been found in '${dir}' directory.`)
      );
}

/**
 * Get svg files descriptions
 * @param {Object} config options
 * @param {string} dir input directory
 * @param {Array} files list of file names in the directory
 * @param {string} output output directory
 * @return {Array}
 */
function getFilesDescriptions(config, dir, files, output) {
  const filesInThisFolder = files
    .filter(
      (name) =>
        regSVGFile.test(name) &&
        !config.exclude.some((regExclude) => regExclude.test(name))
    )
    .map((name) => ({
      inputPath: PATH.resolve(dir, name),
      outputPath: PATH.resolve(output, name),
    }));

  return config.recursive
    ? [].concat(
        filesInThisFolder,
        files
          .filter((name) => checkIsDir(PATH.resolve(dir, name)))
          .map((subFolderName) => {
            const subFolderPath = PATH.resolve(dir, subFolderName);
            const subFolderFiles = FS.readdirSync(subFolderPath);
            const subFolderOutput = PATH.resolve(output, subFolderName);
            return getFilesDescriptions(
              config,
              subFolderPath,
              subFolderFiles,
              subFolderOutput
            );
          })
          .reduce((a, b) => [].concat(a, b), [])
      )
    : filesInThisFolder;
}

/**
 * Read SVG file and pass to processing.
 * @param {Object} config options
 * @param {string} file
 * @param {string} output
 * @return {Promise}
 */
function optimizeFile(config, file, output) {
  return FS.promises.readFile(file, 'utf8').then(
    (data) =>
      processSVGData(config, { input: 'file', path: file }, data, output, file),
    (error) => checkOptimizeFileError(config, file, output, error)
  );
}

/**
 * Optimize SVG data.
 * @param {Object} config options
 * @param {string} data SVG content to optimize
 * @param {string} output where to write optimized file
 * @param {string} [input] input file name (being used if output is a directory)
 * @return {Promise}
 */
function processSVGData(config, info, data, output, input) {
  var startTime = Date.now(),
    prevFileSize = Buffer.byteLength(data, 'utf8');

  const result = optimize(data, { ...config, ...info });
  if (result.error) {
    let message = result.error;
    if (result.path != null) {
      message += `\nFile: ${result.path}`;
    }
    throw Error(message);
  }
  if (config.datauri) {
    result.data = encodeSVGDatauri(result.data, config.datauri);
  }
  var resultFileSize = Buffer.byteLength(result.data, 'utf8'),
    processingTime = Date.now() - startTime;
  return writeOutput(input, output, result.data).then(
    function () {
      if (!config.quiet && output != '-') {
        if (input) {
          console.log(`\n${PATH.basename(input)}:`);
        }
        printTimeInfo(processingTime);
        printProfitInfo(prevFileSize, resultFileSize);
      }
    },
    (error) =>
      Promise.reject(
        new Error(
          error.code === 'ENOTDIR'
            ? `Error: output '${output}' is not a directory.`
            : error
        )
      )
  );
}

/**
 * Write result of an optimization.
 * @param {string} input
 * @param {string} output output file name. '-' for stdout
 * @param {string} data data to write
 * @return {Promise}
 */
function writeOutput(input, output, data) {
  if (output == '-') {
    console.log(data);
    return Promise.resolve();
  }

  FS.mkdirSync(PATH.dirname(output), { recursive: true });

  return FS.promises
    .writeFile(output, data, 'utf8')
    .catch((error) => checkWriteFileError(input, output, data, error));
}

/**
 * Write a time taken by optimization.
 * @param {number} time time in milliseconds.
 */
function printTimeInfo(time) {
  console.log(`Done in ${time} ms!`);
}

/**
 * Write optimizing information in human readable format.
 * @param {number} inBytes size before optimization.
 * @param {number} outBytes size after optimization.
 */
function printProfitInfo(inBytes, outBytes) {
  var profitPercents = 100 - (outBytes * 100) / inBytes;

  console.log(
    Math.round((inBytes / 1024) * 1000) / 1000 +
      ' KiB' +
      (profitPercents < 0 ? ' + ' : ' - ') +
      Math.abs(Math.round(profitPercents * 10) / 10) + '%' +
      ' = ' +
      Math.round((outBytes / 1024) * 1000) / 1000 +
      ' KiB'
  );
}

/**
 * Check for errors, if it's a dir optimize the dir.
 * @param {Object} config
 * @param {string} input
 * @param {string} output
 * @param {Error} error
 * @return {Promise}
 */
function checkOptimizeFileError(config, input, output, error) {
  if (error.code == 'EISDIR') {
    return optimizeFolder(config, input, output);
  } else if (error.code == 'ENOENT') {
    return Promise.reject(
      new Error(`Error: no such file or directory '${error.path}'.`)
    );
  }
  return Promise.reject(error);
}

/**
 * Check for saving file error. If the output is a dir, then write file there.
 * @param {string} input
 * @param {string} output
 * @param {string} data
 * @param {Error} error
 * @return {Promise}
 */
function checkWriteFileError(input, output, data, error) {
  if (error.code == 'EISDIR' && input) {
    return FS.promises.writeFile(
      PATH.resolve(output, PATH.basename(input)),
      data,
      'utf8'
    );
  } else {
    return Promise.reject(error);
  }
}

/**
 * Encode plain SVG data string into Data URI string.
 *
 * @param {string} str input string
 * @param {string} type Data URI type
 * @return {string} output string
 */
function encodeSVGDatauri (str, type) {
  var prefix = 'data:image/svg+xml';
  if (!type || type === 'base64') {
    // base64
    prefix += ';base64,';
    str = prefix + Buffer.from(str).toString('base64');
  } else if (type === 'enc') {
    // URI encoded
    str = prefix + ',' + encodeURIComponent(str);
  } else if (type === 'unenc') {
    // unencoded
    str = prefix + ',' + str;
  }
  return str;
};
